Skip to content

fix(tasks): resolve/list tasks beyond the 100-task cap (#743)#824

Merged
frankbria merged 2 commits into
mainfrom
fix/743-task-resolution-truncation
Jul 7, 2026
Merged

fix(tasks): resolve/list tasks beyond the 100-task cap (#743)#824
frankbria merged 2 commits into
mainfrom
fix/743-task-resolution-truncation

Conversation

@frankbria

@frankbria frankbria commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #743 — CLI task resolution/listing silently truncated at 100 tasks.

list_tasks defaults to limit=100 (ordered priority ASC, created_at ASC), and the CLI resolved partial task ids in Python (t.id.startswith(prefix)) over that truncated list. Any task past the first 100 was therefore:

  • unreachable by cf work start/stop/resume/diagnose (and tasks set, work retry, work update-description),
  • invisible in cf tasks list,
  • skipped by bulk cf tasks set --all.

PRD decomposition routinely exceeds 100 tasks, so this was a real data-integrity gap.

Changes

codeframe/core/tasks.py

  • find_by_prefix(workspace, prefix) — resolves task ids at the SQL layer: WHERE id LIKE 'prefix%' ESCAPE '\', no LIMIT, ordered like list_tasks. Returns all matches so callers keep their ambiguity ("multiple tasks match") handling. The user-typed prefix is escaped so a stray LIKE metachar (%/_) can't over-match and resolve the wrong task before a status change. → AC1
  • list_tasks(limit: Optional[int] = 100)None omits the LIMIT clause (uncapped). Default stays 100 so API pagination (tasks_v2) is unchanged. → AC2

codeframe/cli/app.py

  • All 10 prefix-resolution sites now route through find_by_prefix instead of list_tasks + Python startswith.
  • cf tasks list and bulk cf tasks set --all pass limit=None so tasks beyond the cap are listed, counted, and updated.

Acceptance criteria

  • A direct prefix lookup (WHERE id LIKE 'prefix%') bypasses the limit for resolution.
  • Listing/bulk ops raise the cap (limit=None) so tasks beyond 100 are addressable and counted.

Testing

  • New tests/core/test_tasks_truncation.py: with 150 priority-ordered tasks — find_by_prefix resolves task feat: Migrate web-ui to shadcn/ui Nova design system #150 (past the cap), list_tasks(limit=None) counts all 150, default still caps at 100, and LIKE-wildcard prefixes (%, _) don't over-match.
  • ruff check clean.

Known limitations / out of scope

Summary by CodeRabbit

  • Bug Fixes
    • Partial task IDs now resolve more reliably across task and work commands.
    • Task lists and bulk updates now include items beyond the previous 100-item cap when needed.
    • Matching behavior is more consistent for ambiguous, missing, or special-character task IDs.

CLI resolved id-prefixes in Python over list_tasks (capped at 100,
priority-ordered), so any task past the cap was unreachable by
cf work start/stop/resume/diagnose, invisible in 'tasks list', and
skipped by bulk 'tasks set --all'.

- core: add find_by_prefix() — SQL 'id LIKE prefix% ESCAPE' lookup with
  no LIMIT (user-typed prefix escaped so a stray wildcard can't
  over-match). Route all 10 CLI prefix-resolution sites through it.
- core: list_tasks(limit=None) omits the LIMIT clause; default stays 100
  for API pagination. 'tasks list' and bulk 'set --all' pass limit=None.

Closes #743
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 50 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 77ff1e03-22f7-44e7-b625-96d52efa5302

📥 Commits

Reviewing files that changed from the base of the PR and between c4fbbaf and 8af1554.

📒 Files selected for processing (2)
  • codeframe/cli/app.py
  • tests/core/test_tasks_truncation.py

Walkthrough

Adds a find_by_prefix lookup function using SQL LIKE with escaping to resolve partial task IDs without a result cap, and makes list_tasks's limit parameter optional (None removes the LIMIT clause). Updates multiple CLI commands to use these, plus adds regression tests.

Changes

Task truncation fix: prefix resolution and uncapped listing

Layer / File(s) Summary
Core lookup and listing changes
codeframe/core/tasks.py
Adds find_by_prefix(workspace, prefix) using SQL LIKE with escaping, ordered by priority then created_at; makes list_tasks limit parameter Optional[int], omitting LIMIT when None.
CLI single-task resolution
codeframe/cli/app.py
Updates tasks set, tasks delete, and work start/resume/stop/diagnose/retry/update-description/follow/batch run to resolve partial task IDs via tasks.find_by_prefix instead of filtering with startswith.
CLI bulk/list uncapped listing
codeframe/cli/app.py
Updates tasks list and tasks set --all to call tasks.list_tasks(..., limit=None) so results beyond the default 100-item cap are included.
Regression tests
tests/core/test_tasks_truncation.py
Adds fixtures creating 150 tasks and tests verifying find_by_prefix ambiguity/escaping/no-match handling and list_tasks default-vs-limit=None behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Poem

A hundred tasks once hit a wall,
the hundred-first got lost, that's all!
Now prefixes hop past the cap,
no task falls through the truncation gap.
🐇 With LIKE and ESCAPE, hop-hop-hooray,
every carrot ID found today!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main fix: task lookup and listing beyond the previous 100-task cap.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/743-task-resolution-truncation

Comment @coderabbitai help to get the list of available commands.

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review

Solid fix, and the diff matches the PR description exactly (no prior CodeFrame reviews on this PR to reconcile with — only CodeRabbit's auto-summary so far). Went through both call sites and the new test file in detail.

Correctness

  • find_by_prefix's SQL column list matches get()/list_tasks()/_row_to_task exactly, so no silent column-order bug there.
  • LIKE-metachar escaping (\\\, %\%, _\_, applied in that order so the backslash escape doesn't get re-escaped) is correct, and ESCAPE '\' is valid SQLite syntax.
  • Confirmed all 10 t.id.startswith(...) call sites in codeframe/cli/app.py were converted to find_by_prefix — grepped for the old pattern and nothing remains.
  • tasks_v2.py's GET /api/v2/tasks always passes an explicit limit (FastAPI Query(100, ge=1, le=1000)), so the list_tasks(limit=None) default-preserving change is safe for the API pagination surface, as claimed.
  • tasks_set's bulk vs. single-task variable scoping reads correctly post-diff — all_workspace_tasks is now only built (with limit=None) inside the --all branch; single-task mode goes straight through find_by_prefix.

Minor: test coverage nit
test_find_by_prefix_returns_all_ambiguous_matches (tests/core/test_tasks_truncation.py) picks shared = a.id[:1] (first hex char of a random UUID) and asserts b.id in ids or b.id[0] != shared. Since b.id[0] == shared only happens for ~1/16 of runs, the "ambiguous match" branch is only actually exercised in a small fraction of test runs — the rest of the time the assertion is trivially true without touching the code path the test name promises. Not a flakiness risk (it never fails), just a coverage gap. Consider forcing the collision deterministically, e.g. by prefix-matching against a fixed/mocked id or retrying until a collision is generated, so the ambiguity path is exercised every run.

Out of scope, flagging for awareness only
codeframe/cli/app.py has four other spots (b.id.startswith(batch_id) in the batch commands, e.g. around lines 3955/4100/4169/4368) with the exact same bug shape: conductor.list_batches(workspace, limit=100) then a Python prefix filter. Same class of issue as #743 but for batches instead of tasks — outside this PR's stated scope (which is tasks-only per the issue and the "Known limitations" section), but worth a follow-up issue if there's any realistic path to >100 batches in a workspace.

Nothing else to flag — no security concerns (SQL is fully parameterized, LIKE input is escaped), no perf concerns at this scale, and the new test file otherwise covers the stated ACs well (default cap preserved, limit=None unbounded, wildcard-escaping, status-filtered uncapped listing).

@frankbria

Copy link
Copy Markdown
Owner Author

Demo — both acceptance criteria (real CLI, workspace seeded with 101 tasks)

Seeded 101 tasks with priorities 0..100. Task c854532f ("Task 100", priority 100) sorts last under ORDER BY priority ASC — exactly the row the old LIMIT 100 + Python startswith resolution dropped.

AC2 — listing/counting past the cap

$ cf tasks list
│ c854532f │ BACKLOG │ 100 │  -   │ Task 100 │   ← previously invisible
└──────────┴─────────┴─────┴──────┴──────────┘
Total: 101 | BACKLOG: 101

Old behavior: 100 rows, Task 100 missing.

AC1 — resolving + mutating a task past the cap by id-prefix

$ cf tasks set status c854532f READY
Task updated
Task 100
Status: BACKLOG -> READY        (exit 0)

$ cf tasks list --status READY
│ c854532f │ READY  │ 100 │  -   │ Task 100 │
Total: 1 | BACKLOG: 100 | READY: 1

Old behavior: Error: No task found matching 'c854532f'.

Backend suite (test_tasks_truncation.py + test_tasks_edge_cases.py) and ruff pass locally; CI is the authoritative gate.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
codeframe/cli/app.py (2)

2248-2264: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

tasks delete --all confirm prompt undercounts tasks beyond the cap.

task_list = tasks.list_tasks(workspace) here is capped at 100 by default and is only used for the "Delete all N tasks?" confirmation count — actual deletion goes through tasks.delete_all(workspace), which is uncapped. If the workspace has more than 100 tasks, the user sees an inaccurate (too-low) count before confirming a destructive bulk operation.

🐛 Proposed fix
         if all_tasks_flag:
             # Delete all tasks
-            task_list = tasks.list_tasks(workspace)
+            task_list = tasks.list_tasks(workspace, limit=None)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@codeframe/cli/app.py` around lines 2248 - 2264, The `tasks delete --all`
confirmation in `app.py` is using `tasks.list_tasks(workspace)`, which is capped
and can undercount the real number of tasks. Update the `all_tasks_flag` branch
to compute the total task count with an uncapped source before calling
`typer.confirm`, so the prompt reflects the full number that
`tasks.delete_all(workspace)` will remove. Keep the change localized to the
`all_tasks_flag` deletion flow and preserve the existing cancel/force behavior.

3774-3789: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

batch run --all-ready/--all-blocked still capped at 100.

ids_to_execute for --all-ready and --all-blocked is built from tasks_module.list_tasks(workspace, status=...) without limit=None. This reintroduces the exact #743 truncation the rest of this PR fixes: batch execution silently skips READY/BLOCKED tasks beyond the default 100-task cap, unlike tasks list and tasks set --all which were updated.

🐛 Proposed fix
         if all_ready:
-            ready_tasks = tasks_module.list_tasks(workspace, status=TaskStatus.READY)
+            ready_tasks = tasks_module.list_tasks(workspace, status=TaskStatus.READY, limit=None)
             if not ready_tasks:
                 console.print("[yellow]No READY tasks found[/yellow]")
                 return
             ids_to_execute = [t.id for t in ready_tasks]
             console.print(f"Found {len(ids_to_execute)} READY tasks")
         elif all_blocked:
             from codeframe.core import runtime
-            blocked_tasks = tasks_module.list_tasks(workspace, status=TaskStatus.BLOCKED)
+            blocked_tasks = tasks_module.list_tasks(workspace, status=TaskStatus.BLOCKED, limit=None)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@codeframe/cli/app.py` around lines 3774 - 3789, The `batch run` paths for
`all_ready` and `all_blocked` are still using the default
`tasks_module.list_tasks(...)` cap, so only the first 100 tasks are collected.
Update the `ids_to_execute` निर्माण in `app.py` to pass an unlimited fetch (use
the same `limit=None` pattern as the other bulk commands) for both READY and
BLOCKED branches in the batch-run logic. Keep the fix localized around the
`all_ready` / `all_blocked` handling so the full task set is executed.
🧹 Nitpick comments (3)
codeframe/core/tasks.py (2)

217-225: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Uncapped LIKE prefix scan may degrade at scale.

SQLite's LIKE optimizer only uses an index for prefix matches when the column uses BINARY collation with case_sensitive_like ON, or NOCASE collation with the pragma OFF — by default LIKE is case-insensitive and won't use the index, forcing a full table scan. Since this PR's whole purpose is to make lookups work for workspaces beyond 100+ tasks (regression tests already cover 150), this scan will grow linearly with total workspace size on every prefix resolution.

Consider using GLOB (case-sensitive by default, can use an index on a BINARY-collated column) since task ids are UUIDs and case sensitivity is actually desirable here, or add a range predicate (id >= ? AND id < ?) computed from the prefix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@codeframe/core/tasks.py` around lines 217 - 225, The prefix lookup in tasks
query uses LIKE with an escaped prefix, which can trigger a full table scan as
workspace size grows. Update the task resolution query in tasks.py to use an
index-friendly approach such as GLOB for the id prefix match, or replace the
LIKE predicate with a bounded range check on id while keeping the existing
workspace_id filter and ordering intact.

219-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated SELECT column list (3x).

The full 20-column list is now repeated verbatim in find_by_prefix and both branches of list_tasks. Extracting it to a module-level constant would reduce risk of drift when the schema changes.

♻️ Proposed refactor
+_TASK_COLUMNS = (
+    "id, workspace_id, prd_id, title, description, status, priority, depends_on, "
+    "estimated_hours, complexity_score, uncertainty_level, created_at, updated_at, "
+    "github_issue_number, parent_id, lineage, is_leaf, hierarchical_id, "
+    "requirement_ids, external_url, auto_close_github_issue"
+)

Then reference _TASK_COLUMNS via f-string in all three SELECT statements.

Also applies to: 347-347, 361-361

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@codeframe/core/tasks.py` at line 219, The SELECT column list is duplicated
across find_by_prefix and both branches of list_tasks, so extract the repeated
task column set into a module-level constant (for example, _TASK_COLUMNS) in
tasks.py and reference it in all three queries. Update the existing SELECT
statements in the affected task-related methods to use the shared constant so
the schema only needs to be changed in one place.
tests/core/test_tasks_truncation.py (1)

49-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Ambiguity assertion is tautological in the common case.

shared = a.id[:1] picks a single hex character; two random UUIDs share it only ~1/16 of the time. When they don't, b.id[0] != shared is true regardless of matches, so the assertion collapses to just a.id in ids — the test rarely actually exercises multi-match/ambiguity behavior despite its name and docstring.

Force ambiguity deterministically instead of relying on chance, e.g. use an empty prefix (matches everything via LIKE '%') or compute the actual shared prefix between the two created ids and assert both appear.

♻️ Proposed fix using a guaranteed-ambiguous prefix
     def test_find_by_prefix_returns_all_ambiguous_matches(self, workspace):
         """Ambiguity detection still works — callers report >1 match."""
         a = tasks.create(workspace, title="a")
         b = tasks.create(workspace, title="b")
-        shared = a.id[:1]
-        matches = tasks.find_by_prefix(workspace, shared)
-        ids = {t.id for t in matches}
-        assert a.id in ids and (b.id in ids or b.id[0] != shared)
+        # Empty prefix matches every id, guaranteeing ambiguity deterministically.
+        matches = tasks.find_by_prefix(workspace, "")
+        ids = {t.id for t in matches}
+        assert a.id in ids and b.id in ids
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/core/test_tasks_truncation.py` around lines 49 - 56, The ambiguity test
in test_find_by_prefix_returns_all_ambiguous_matches is effectively
probabilistic because shared = a.id[:1] may not produce multiple matches, so the
assertion can pass without exercising ambiguity. Update the test to use a
deterministic ambiguous prefix in tasks.find_by_prefix, such as an empty prefix
or a prefix computed from the created ids, and assert the expected multiple ids
are returned so the test always validates the multi-match behavior described by
the docstring.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@codeframe/cli/app.py`:
- Around line 2248-2264: The `tasks delete --all` confirmation in `app.py` is
using `tasks.list_tasks(workspace)`, which is capped and can undercount the real
number of tasks. Update the `all_tasks_flag` branch to compute the total task
count with an uncapped source before calling `typer.confirm`, so the prompt
reflects the full number that `tasks.delete_all(workspace)` will remove. Keep
the change localized to the `all_tasks_flag` deletion flow and preserve the
existing cancel/force behavior.
- Around line 3774-3789: The `batch run` paths for `all_ready` and `all_blocked`
are still using the default `tasks_module.list_tasks(...)` cap, so only the
first 100 tasks are collected. Update the `ids_to_execute` निर्माण in `app.py`
to pass an unlimited fetch (use the same `limit=None` pattern as the other bulk
commands) for both READY and BLOCKED branches in the batch-run logic. Keep the
fix localized around the `all_ready` / `all_blocked` handling so the full task
set is executed.

---

Nitpick comments:
In `@codeframe/core/tasks.py`:
- Around line 217-225: The prefix lookup in tasks query uses LIKE with an
escaped prefix, which can trigger a full table scan as workspace size grows.
Update the task resolution query in tasks.py to use an index-friendly approach
such as GLOB for the id prefix match, or replace the LIKE predicate with a
bounded range check on id while keeping the existing workspace_id filter and
ordering intact.
- Line 219: The SELECT column list is duplicated across find_by_prefix and both
branches of list_tasks, so extract the repeated task column set into a
module-level constant (for example, _TASK_COLUMNS) in tasks.py and reference it
in all three queries. Update the existing SELECT statements in the affected
task-related methods to use the shared constant so the schema only needs to be
changed in one place.

In `@tests/core/test_tasks_truncation.py`:
- Around line 49-56: The ambiguity test in
test_find_by_prefix_returns_all_ambiguous_matches is effectively probabilistic
because shared = a.id[:1] may not produce multiple matches, so the assertion can
pass without exercising ambiguity. Update the test to use a deterministic
ambiguous prefix in tasks.find_by_prefix, such as an empty prefix or a prefix
computed from the created ids, and assert the expected multiple ids are returned
so the test always validates the multi-match behavior described by the
docstring.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c3f127b7-d4bc-4ae9-b3f6-4620389ac253

📥 Commits

Reviewing files that changed from the base of the PR and between bc0e6d6 and c4fbbaf.

📒 Files selected for processing (3)
  • codeframe/cli/app.py
  • codeframe/core/tasks.py
  • tests/core/test_tasks_truncation.py

Address review findings on PR #824:
- batch run --all-ready/--all-blocked: pass limit=None so batches include
  READY/BLOCKED tasks beyond the 100 cap (was reintroducing the #743
  truncation for batch execution).
- tasks delete --all: count the confirm prompt with limit=None so it
  matches the uncapped delete_all() (avoids undercounting before a
  destructive op).
- test: make the ambiguity test deterministic (empty prefix matches all)
  instead of relying on a ~1/16 first-hex-char collision.
@frankbria

Copy link
Copy Markdown
Owner Author

Review triage (CodeRabbit + claude)

Thanks both. Dispositions:

Fixed in 8af1554

  • 🟠 Major — batch run --all-ready/--all-blocked still capped (CodeRabbit): confirmed — it re-introduced the [P1.16] CLI task resolution/listing silently truncates at 100 tasks #743 truncation for batch execution. Both branches now pass limit=None.
  • 🟡 Minor — tasks delete --all confirm count undercounts (CodeRabbit): confirmed — count now uses limit=None to match the uncapped delete_all().
  • 🟡 Minor — tautological ambiguity test (both): now uses an empty prefix (LIKE '%') so the multi-match path is exercised every run instead of ~1/16.

Deferred (Trivial), with reasons

  • 🔵 LIKE prefix → full table scan (perf): correctness is unaffected (ids are lowercase UUIDs, so case-insensitive LIKE never mis-matches), and this is strictly faster than the code it replaces (which loaded all rows and scanned in Python). A range/GLOB rewrite is premature optimization at realistic workspace sizes — deferring.
  • 🔵 Extract shared _TASK_COLUMNS constant: the 20-column SELECT is already duplicated across ~5 pre-existing functions (get, get_by_external_url, list_by_status, …). Extracting it for only the 3 this PR touches adds inconsistency; a file-wide dedup is a separate refactor, out of scope for a bug fix.

Follow-up filed

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review (re-review of 8af1554)

Confirmed the second commit resolves everything from the first round cleanly:

  • batch run --all-ready / --all-blocked now pass limit=None (codeframe/cli/app.py:3777,3785) - the batch-truncation regression is gone.
  • tasks delete --all confirm count now uses limit=None (codeframe/cli/app.py:2251), matching the uncapped delete_all().
  • The ambiguity test now forces the collision deterministically via an empty prefix instead of relying on a 1/16 chance - exercises the intended code path every run.

Also re-grepped for the old .id.startswith( pattern across codeframe/cli/app.py: the only remaining hits are the four batch_id sites, which is exactly the scope of the follow-up issue (#825) already filed in the triage comment - nothing task-related was missed.

New finding - same bug class survives in tasks generate --overwrite

codeframe/cli/app.py:1854-1856 and :1892/:1906-1909 (the tasks_generate command, untouched by this PR) still call the capped tasks.list_tasks(workspace) (default limit=100) to snapshot original_ids before regeneration and to delete them after generation succeeds:

original_ids = set of t.id for t in tasks.list_tasks(workspace), if overwrite else empty set
...
if overwrite and original_ids:
    for tid in original_ids: tasks.delete(workspace, tid)

In a workspace with more than 100 existing tasks - which the PR description itself says is routine for PRD decomposition - cf tasks generate --overwrite will only clear the first 100 (priority ASC, created_at ASC). The remaining pre-existing tasks silently survive alongside the freshly generated set, so --overwrite no longer actually overwrites. It is the same root cause as #743 (an unqualified list_tasks default cap) but in a codepath this PR does not touch and that is not mentioned in the Known limitations section. It is not a regression from this PR, but it is squarely in-theme and a one-line fix (limit=None on both calls) - worth doing here or filing alongside #825 rather than leaving it to be rediscovered separately.

Everything else - SQL parameterization, LIKE-escaping order, the _row_to_task column mapping in find_by_prefix, and test coverage for the two ACs - still holds up as noted in my earlier pass. No other bugs, security, or perf concerns.

@frankbria

Copy link
Copy Markdown
Owner Author

CodeRabbit's latest pass re-surfaces the same two Functional Correctness findings (delete-all count, batch --all-ready/--all-blocked) as outside-diff comments — both are already fixed in 8af1554 (verified in HEAD: list_tasks(..., limit=None) at the delete-all count and both batch branches). No outstanding Critical/Major. Merging.

@frankbria frankbria merged commit dbcc7ec into main Jul 7, 2026
11 checks passed
@frankbria frankbria deleted the fix/743-task-resolution-truncation branch July 7, 2026 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P1.16] CLI task resolution/listing silently truncates at 100 tasks

1 participant